﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Isam.Esent.Interop;

namespace ThumbcacheHelper
{
    class EdbSeeker
    {
        const string tblName = "SystemIndex_0A";
        const string pathColName = "System_ItemPathDisplay";
        const string thumbColName = "System_ThumbnailCacheId";
        const string thumbIdx = "System_ThumbnailCacheId0";        
        int pageSize;

        JET_INSTANCE instance;
        JET_SESID sesid;
        JET_DBID dbid;
        JET_TABLEID tableid;
        JET_COLUMNID pathColId;
        JET_COLUMNID thumbColId;
        string edbPath;

        public string Prepare(string EdbPath)
        {
            edbPath = EdbPath;
            string r = null;

            try
            {
                Api.JetGetDatabaseFileInfo(EdbPath, out pageSize, JET_DbInfo.PageSize);
                Api.JetSetSystemParameter(JET_INSTANCE.Nil, JET_SESID.Nil, JET_param.DatabasePageSize, pageSize, null);

                Api.JetCreateInstance(out instance, "edbseeker");
                Api.JetInit(ref instance);
                Api.JetBeginSession(instance, out sesid, null, null);

                Api.JetAttachDatabase(sesid, edbPath, AttachDatabaseGrbit.DeleteCorruptIndexes);
                Api.JetOpenDatabase(sesid, edbPath, null, out dbid, OpenDatabaseGrbit.Exclusive);
                Api.JetOpenTable(sesid, dbid, tblName, null, 0, OpenTableGrbit.Updatable, out tableid);

                pathColId = Api.GetTableColumnid(sesid, tableid, pathColName);
                thumbColId = Api.GetTableColumnid(sesid, tableid, thumbColName);
            }
            catch (EsentPageSizeMismatchException ex)
            {
                StringBuilder sb = new StringBuilder(ex.Message);
                sb.AppendFormat("{0}This is very normally caused by remnants from a previous use of this program.", Environment.NewLine);
                sb.AppendFormat("{0}Try deleting any unrequired files from the working folder. The only required files are as follows:", Environment.NewLine);
                sb.AppendFormat("{0}- ThumbcacheHelper.exe", Environment.NewLine);
                sb.AppendFormat("{0}- Esent.Interop.dll", Environment.NewLine);
                sb.AppendFormat("{0}- ObjectListView.dll", Environment.NewLine);
                sb.AppendFormat("{0}All other files can be deleted, but you probably only need to delete any files that start: edb", Environment.NewLine);
                return sb.ToString();
            }
            catch (EsentDatabaseDirtyShutdownException ex)
            {
                StringBuilder sb = new StringBuilder(ex.Message);
                sb.AppendFormat("{0}This is a very common error because the database is constantly in use by Windows.", Environment.NewLine);
                sb.AppendFormat("{0}It can be solved by recovering or repairing the database file.", Environment.NewLine);
                sb.AppendFormat("{0}You will probably need to run esentutl with the same version of Windows as your database file comes from, i.e. Vista or 7.", Environment.NewLine);
                sb.AppendFormat("{0}You can play the transaction log files to the database using esentutl /r", Environment.NewLine);
                sb.AppendFormat("{0}From a very helpful man JS:", Environment.NewLine);
                sb.AppendFormat(@"{0} 1. Copy all log files contained in the tree of %homedrive%\ProgramData\Microsoft\Search\Data\Applications\Windows\ (SystemIndex*.*,  *.log, *.chk (and possibly also *.jrs)) into one directory", Environment.NewLine);
                sb.AppendFormat(@"{0} 2. Run esentutl /R MSS /I /D is run (MSS.LOG was my log file needed for the recovery).", Environment.NewLine);
                sb.AppendFormat("{0}The result should be a recovered database file!", Environment.NewLine);
                sb.AppendFormat("{0}Check 'Recovering a database “out of place”' at http://technet.microsoft.com/en-us/library/aa998074%28v=exchg.65%29.aspx", Environment.NewLine);
                sb.AppendFormat("{0}If Recovery doesn't work you can probable repair the file with: esentutl /p <database-file>", Environment.NewLine);
                sb.AppendFormat("{0}However, data since the last edb update will probably be lost.", Environment.NewLine);                
                return sb.ToString();
            }
            catch (Exception ex)
            {
                r = ex.Message;
            }

            return r;
        }

        public void SetIndex_7()
        {
            Api.JetSetCurrentIndex(sesid, tableid, thumbIdx);
        }

        public void CreateIndex_Vista()
        {
            string idxDef = "+" + thumbColName + "\0\0";
            try
            {
                Api.JetCreateIndex(sesid, tableid, thumbIdx, CreateIndexGrbit.None, idxDef, idxDef.Length, 100);
            }
            catch (EsentIndexDuplicateException)
            {
                // Ignore this error
            }
            Api.JetSetCurrentIndex(sesid, tableid, thumbIdx);
        }

        public string LookupHash_Vista(string Filename, long EntryHash)
        {
            string r = "Not Found";
            byte[] needle;

            try
            {
                needle = new byte[Filename.Length / 2];
                for (int i = 0; i < needle.Length; i++)
                {
                    needle[i] = Convert.ToByte(Filename.Substring(i * 2, 2), 16);
                }
            }
            catch (FormatException)
            {
                needle = BitConverter.GetBytes(EntryHash);
                Array.Reverse(needle);
            }

            Api.JetMakeKey(sesid, tableid, needle, needle.Length, MakeKeyGrbit.NewKey);
                if (Api.TrySeek(sesid, tableid, SeekGrbit.SeekEQ))
                    r = Api.RetrieveColumnAsString(sesid, tableid, pathColId);

            return r;
        }

        public void Close()
        {
            Api.JetCloseTable(sesid, tableid);
            Api.JetCloseDatabase(sesid, dbid, CloseDatabaseGrbit.None);
            Api.JetDetachDatabase(sesid, edbPath);
            Api.JetEndSession(sesid, EndSessionGrbit.None);
            Api.JetTerm(instance);
        }
    }
}
